{
  "nbformat": 4,
  "nbformat_minor": 0,
  "metadata": {
    "colab": {
      "name": "bioNLP2graph.ipynb",
      "provenance": [],
      "authorship_tag": "ABX9TyOWRo9NkSzJ97eMsEiSbaKr",
      "include_colab_link": true
    },
    "kernelspec": {
      "name": "python3",
      "display_name": "Python 3"
    },
    "language_info": {
      "name": "python"
    }
  },
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "view-in-github",
        "colab_type": "text"
      },
      "source": [
        "<a href=\"https://colab.research.google.com/github/tomasonjo/blogs/blob/master/bionlp/bioNLP2graph.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "-uuGuvfarzbf",
        "outputId": "4a9f127d-77df-4d85-c113-183d38d4c4d3"
      },
      "source": [
        "!sudo apt install tesseract-ocr\n",
        "!sudo apt-get install poppler-utils \n",
        "!pip install pytesseract pdf2image zero-shot-re neo4j"
      ],
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Reading package lists... Done\n",
            "Building dependency tree       \n",
            "Reading state information... Done\n",
            "tesseract-ocr is already the newest version (4.00~git2288-10f4998a-2).\n",
            "0 upgraded, 0 newly installed, 0 to remove and 37 not upgraded.\n",
            "Reading package lists... Done\n",
            "Building dependency tree       \n",
            "Reading state information... Done\n",
            "poppler-utils is already the newest version (0.62.0-2ubuntu2.12).\n",
            "0 upgraded, 0 newly installed, 0 to remove and 37 not upgraded.\n",
            "Requirement already satisfied: pytesseract in /usr/local/lib/python3.7/dist-packages (0.3.8)\n",
            "Requirement already satisfied: pdf2image in /usr/local/lib/python3.7/dist-packages (1.16.0)\n",
            "Requirement already satisfied: zero-shot-re in /usr/local/lib/python3.7/dist-packages (0.0.4)\n",
            "Requirement already satisfied: neo4j in /usr/local/lib/python3.7/dist-packages (4.3.7)\n",
            "Requirement already satisfied: Pillow in /usr/local/lib/python3.7/dist-packages (from pytesseract) (7.1.2)\n",
            "Requirement already satisfied: transformers==4.9.2 in /usr/local/lib/python3.7/dist-packages (from zero-shot-re) (4.9.2)\n",
            "Requirement already satisfied: torch==1.9.0 in /usr/local/lib/python3.7/dist-packages (from zero-shot-re) (1.9.0+cu111)\n",
            "Requirement already satisfied: numpy==1.19.1 in /usr/local/lib/python3.7/dist-packages (from zero-shot-re) (1.19.1)\n",
            "Requirement already satisfied: jupyterlab==2.2.9 in /usr/local/lib/python3.7/dist-packages (from zero-shot-re) (2.2.9)\n",
            "Requirement already satisfied: jinja2>=2.10 in /usr/local/lib/python3.7/dist-packages (from jupyterlab==2.2.9->zero-shot-re) (2.11.3)\n",
            "Requirement already satisfied: tornado!=6.0.0,!=6.0.1,!=6.0.2 in /usr/local/lib/python3.7/dist-packages (from jupyterlab==2.2.9->zero-shot-re) (5.1.1)\n",
            "Requirement already satisfied: notebook>=4.3.1 in /usr/local/lib/python3.7/dist-packages (from jupyterlab==2.2.9->zero-shot-re) (5.3.1)\n",
            "Requirement already satisfied: jupyterlab-server<2.0,>=1.1.5 in /usr/local/lib/python3.7/dist-packages (from jupyterlab==2.2.9->zero-shot-re) (1.2.0)\n",
            "Requirement already satisfied: typing-extensions in /usr/local/lib/python3.7/dist-packages (from torch==1.9.0->zero-shot-re) (3.7.4.3)\n",
            "Requirement already satisfied: huggingface-hub==0.0.12 in /usr/local/lib/python3.7/dist-packages (from transformers==4.9.2->zero-shot-re) (0.0.12)\n",
            "Requirement already satisfied: sacremoses in /usr/local/lib/python3.7/dist-packages (from transformers==4.9.2->zero-shot-re) (0.0.46)\n",
            "Requirement already satisfied: tqdm>=4.27 in /usr/local/lib/python3.7/dist-packages (from transformers==4.9.2->zero-shot-re) (4.62.3)\n",
            "Requirement already satisfied: tokenizers<0.11,>=0.10.1 in /usr/local/lib/python3.7/dist-packages (from transformers==4.9.2->zero-shot-re) (0.10.3)\n",
            "Requirement already satisfied: pyyaml>=5.1 in /usr/local/lib/python3.7/dist-packages (from transformers==4.9.2->zero-shot-re) (6.0)\n",
            "Requirement already satisfied: packaging in /usr/local/lib/python3.7/dist-packages (from transformers==4.9.2->zero-shot-re) (21.0)\n",
            "Requirement already satisfied: regex!=2019.12.17 in /usr/local/lib/python3.7/dist-packages (from transformers==4.9.2->zero-shot-re) (2019.12.20)\n",
            "Requirement already satisfied: requests in /usr/local/lib/python3.7/dist-packages (from transformers==4.9.2->zero-shot-re) (2.23.0)\n",
            "Requirement already satisfied: importlib-metadata in /usr/local/lib/python3.7/dist-packages (from transformers==4.9.2->zero-shot-re) (4.8.1)\n",
            "Requirement already satisfied: filelock in /usr/local/lib/python3.7/dist-packages (from transformers==4.9.2->zero-shot-re) (3.3.0)\n",
            "Requirement already satisfied: MarkupSafe>=0.23 in /usr/local/lib/python3.7/dist-packages (from jinja2>=2.10->jupyterlab==2.2.9->zero-shot-re) (2.0.1)\n",
            "Requirement already satisfied: jsonschema>=3.0.1 in /usr/local/lib/python3.7/dist-packages (from jupyterlab-server<2.0,>=1.1.5->jupyterlab==2.2.9->zero-shot-re) (4.1.2)\n",
            "Requirement already satisfied: json5 in /usr/local/lib/python3.7/dist-packages (from jupyterlab-server<2.0,>=1.1.5->jupyterlab==2.2.9->zero-shot-re) (0.9.6)\n",
            "Requirement already satisfied: attrs>=17.4.0 in /usr/local/lib/python3.7/dist-packages (from jsonschema>=3.0.1->jupyterlab-server<2.0,>=1.1.5->jupyterlab==2.2.9->zero-shot-re) (21.2.0)\n",
            "Requirement already satisfied: pyrsistent!=0.17.0,!=0.17.1,!=0.17.2,>=0.14.0 in /usr/local/lib/python3.7/dist-packages (from jsonschema>=3.0.1->jupyterlab-server<2.0,>=1.1.5->jupyterlab==2.2.9->zero-shot-re) (0.18.0)\n",
            "Requirement already satisfied: jupyter-client>=5.2.0 in /usr/local/lib/python3.7/dist-packages (from notebook>=4.3.1->jupyterlab==2.2.9->zero-shot-re) (5.3.5)\n",
            "Requirement already satisfied: jupyter-core>=4.4.0 in /usr/local/lib/python3.7/dist-packages (from notebook>=4.3.1->jupyterlab==2.2.9->zero-shot-re) (4.8.1)\n",
            "Requirement already satisfied: terminado>=0.8.1 in /usr/local/lib/python3.7/dist-packages (from notebook>=4.3.1->jupyterlab==2.2.9->zero-shot-re) (0.12.1)\n",
            "Requirement already satisfied: ipython-genutils in /usr/local/lib/python3.7/dist-packages (from notebook>=4.3.1->jupyterlab==2.2.9->zero-shot-re) (0.2.0)\n",
            "Requirement already satisfied: nbconvert in /usr/local/lib/python3.7/dist-packages (from notebook>=4.3.1->jupyterlab==2.2.9->zero-shot-re) (5.6.1)\n",
            "Requirement already satisfied: ipykernel in /usr/local/lib/python3.7/dist-packages (from notebook>=4.3.1->jupyterlab==2.2.9->zero-shot-re) (4.10.1)\n",
            "Requirement already satisfied: traitlets>=4.2.1 in /usr/local/lib/python3.7/dist-packages (from notebook>=4.3.1->jupyterlab==2.2.9->zero-shot-re) (5.1.0)\n",
            "Requirement already satisfied: Send2Trash in /usr/local/lib/python3.7/dist-packages (from notebook>=4.3.1->jupyterlab==2.2.9->zero-shot-re) (1.8.0)\n",
            "Requirement already satisfied: nbformat in /usr/local/lib/python3.7/dist-packages (from notebook>=4.3.1->jupyterlab==2.2.9->zero-shot-re) (5.1.3)\n",
            "Requirement already satisfied: pyzmq>=13 in /usr/local/lib/python3.7/dist-packages (from jupyter-client>=5.2.0->notebook>=4.3.1->jupyterlab==2.2.9->zero-shot-re) (22.3.0)\n",
            "Requirement already satisfied: python-dateutil>=2.1 in /usr/local/lib/python3.7/dist-packages (from jupyter-client>=5.2.0->notebook>=4.3.1->jupyterlab==2.2.9->zero-shot-re) (2.8.2)\n",
            "Requirement already satisfied: pyparsing>=2.0.2 in /usr/local/lib/python3.7/dist-packages (from packaging->transformers==4.9.2->zero-shot-re) (2.4.7)\n",
            "Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.7/dist-packages (from python-dateutil>=2.1->jupyter-client>=5.2.0->notebook>=4.3.1->jupyterlab==2.2.9->zero-shot-re) (1.15.0)\n",
            "Requirement already satisfied: ptyprocess in /usr/local/lib/python3.7/dist-packages (from terminado>=0.8.1->notebook>=4.3.1->jupyterlab==2.2.9->zero-shot-re) (0.7.0)\n",
            "Requirement already satisfied: pytz in /usr/local/lib/python3.7/dist-packages (from neo4j) (2018.9)\n",
            "Requirement already satisfied: zipp>=0.5 in /usr/local/lib/python3.7/dist-packages (from importlib-metadata->transformers==4.9.2->zero-shot-re) (3.6.0)\n",
            "Requirement already satisfied: ipython>=4.0.0 in /usr/local/lib/python3.7/dist-packages (from ipykernel->notebook>=4.3.1->jupyterlab==2.2.9->zero-shot-re) (5.5.0)\n",
            "Requirement already satisfied: decorator in /usr/local/lib/python3.7/dist-packages (from ipython>=4.0.0->ipykernel->notebook>=4.3.1->jupyterlab==2.2.9->zero-shot-re) (4.4.2)\n",
            "Requirement already satisfied: pygments in /usr/local/lib/python3.7/dist-packages (from ipython>=4.0.0->ipykernel->notebook>=4.3.1->jupyterlab==2.2.9->zero-shot-re) (2.6.1)\n",
            "Requirement already satisfied: pexpect in /usr/local/lib/python3.7/dist-packages (from ipython>=4.0.0->ipykernel->notebook>=4.3.1->jupyterlab==2.2.9->zero-shot-re) (4.8.0)\n",
            "Requirement already satisfied: pickleshare in /usr/local/lib/python3.7/dist-packages (from ipython>=4.0.0->ipykernel->notebook>=4.3.1->jupyterlab==2.2.9->zero-shot-re) (0.7.5)\n",
            "Requirement already satisfied: simplegeneric>0.8 in /usr/local/lib/python3.7/dist-packages (from ipython>=4.0.0->ipykernel->notebook>=4.3.1->jupyterlab==2.2.9->zero-shot-re) (0.8.1)\n",
            "Requirement already satisfied: prompt-toolkit<2.0.0,>=1.0.4 in /usr/local/lib/python3.7/dist-packages (from ipython>=4.0.0->ipykernel->notebook>=4.3.1->jupyterlab==2.2.9->zero-shot-re) (1.0.18)\n",
            "Requirement already satisfied: setuptools>=18.5 in /usr/local/lib/python3.7/dist-packages (from ipython>=4.0.0->ipykernel->notebook>=4.3.1->jupyterlab==2.2.9->zero-shot-re) (57.4.0)\n",
            "Requirement already satisfied: wcwidth in /usr/local/lib/python3.7/dist-packages (from prompt-toolkit<2.0.0,>=1.0.4->ipython>=4.0.0->ipykernel->notebook>=4.3.1->jupyterlab==2.2.9->zero-shot-re) (0.2.5)\n",
            "Requirement already satisfied: mistune<2,>=0.8.1 in /usr/local/lib/python3.7/dist-packages (from nbconvert->notebook>=4.3.1->jupyterlab==2.2.9->zero-shot-re) (0.8.4)\n",
            "Requirement already satisfied: bleach in /usr/local/lib/python3.7/dist-packages (from nbconvert->notebook>=4.3.1->jupyterlab==2.2.9->zero-shot-re) (4.1.0)\n",
            "Requirement already satisfied: defusedxml in /usr/local/lib/python3.7/dist-packages (from nbconvert->notebook>=4.3.1->jupyterlab==2.2.9->zero-shot-re) (0.7.1)\n",
            "Requirement already satisfied: entrypoints>=0.2.2 in /usr/local/lib/python3.7/dist-packages (from nbconvert->notebook>=4.3.1->jupyterlab==2.2.9->zero-shot-re) (0.3)\n",
            "Requirement already satisfied: pandocfilters>=1.4.1 in /usr/local/lib/python3.7/dist-packages (from nbconvert->notebook>=4.3.1->jupyterlab==2.2.9->zero-shot-re) (1.5.0)\n",
            "Requirement already satisfied: testpath in /usr/local/lib/python3.7/dist-packages (from nbconvert->notebook>=4.3.1->jupyterlab==2.2.9->zero-shot-re) (0.5.0)\n",
            "Requirement already satisfied: webencodings in /usr/local/lib/python3.7/dist-packages (from bleach->nbconvert->notebook>=4.3.1->jupyterlab==2.2.9->zero-shot-re) (0.5.1)\n",
            "Requirement already satisfied: urllib3!=1.25.0,!=1.25.1,<1.26,>=1.21.1 in /usr/local/lib/python3.7/dist-packages (from requests->transformers==4.9.2->zero-shot-re) (1.24.3)\n",
            "Requirement already satisfied: chardet<4,>=3.0.2 in /usr/local/lib/python3.7/dist-packages (from requests->transformers==4.9.2->zero-shot-re) (3.0.4)\n",
            "Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.7/dist-packages (from requests->transformers==4.9.2->zero-shot-re) (2021.5.30)\n",
            "Requirement already satisfied: idna<3,>=2.5 in /usr/local/lib/python3.7/dist-packages (from requests->transformers==4.9.2->zero-shot-re) (2.10)\n",
            "Requirement already satisfied: click in /usr/local/lib/python3.7/dist-packages (from sacremoses->transformers==4.9.2->zero-shot-re) (7.1.2)\n",
            "Requirement already satisfied: joblib in /usr/local/lib/python3.7/dist-packages (from sacremoses->transformers==4.9.2->zero-shot-re) (1.0.1)\n"
          ]
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ukjRkys6nWdQ"
      },
      "source": [
        "# Construct a biomedical knowledge graph with NLP\n",
        "## Learn how to combine OCR, named entity linking, relation extraction and external enrichment databases to construct a biomedical knowledge graph\n",
        "I have already demonstrated how to create a knowledge graph out of a Wikipedia page. However, since the post got a lot of attention, I've decided to explore other domains where using NLP techniques to construct a knowledge graph makes sense. In my opinion, the biomedical field is a prime example where representing the data as a graph makes sense as you are often analyzing interactions and relations between genes, diseases, drugs, proteins, and more.\n",
        "\n",
        "You could have a team of domain experts map all of those connections between drugs, diseases, and other biomedical concepts for you. But, unfortunately, not many of us can afford to hire a team of medical doctors to do the work for us. In that case, we can resort to using NLP techniques to extract those relationships automatically. The good part is that we can use an NLP pipeline to read all of the research papers out there, and the bad part is that not all obtained results will be perfect. However, given that I don't have a team of scientists ready at my side to extract relations manually, I will resort to using NLP techniques to construct a biomedical knowledge graph of my own.\n",
        "I will be using a single research paper in this blog post to walk you through all the steps required to construct a biomedical knowledge graph.\n",
        "\n",
        "I will be using the Tissue Engineering of Skin Regeneration and Hair Growth paper written by Mohammadreza Ahmadi. The PDF version of the article is available under the CC0 1.0 license. We will go through the following steps to construct a knowledge graph:\n",
        "* Reading a PDF document with OCR\n",
        "* Text preprocessing\n",
        "* Biomedical concept recognition and linking\n",
        "* Relation extraction\n",
        "* External database enrichment\n",
        "\n",
        "We will be using Neo4j, a graph database that features the labeled property graph model, to store our graph. Each article can have one or more authors. We will split the article content into sentences and use NLP to extract both medical entities and their relationships. It might be a bit counter-intuitive that we will store the relations between entities as intermediate nodes instead of relationships. The critical factor behind this decision is that we want to have an audit trail of the source text from which the relation was extracted. With the labeled property graph model, you can't have a relationship pointing to another relationship. For this reason, we refactor the connection between medical concepts into an intermediate node. This will also allow a domain expert to evaluate if a relation was correctly extracted or not.\n",
        "Along the way, I will also demonstrate applications of using the constructed graph to search and analyze stored information.\n",
        "Let's dive right into it!\n",
        "# Reading a PDF document with OCR\n",
        "As mentioned, the PDF version of the research paper is accessible to the public under the CC0 1.0 license, which means we can easily download it with Python. We will be using the pytesseract library to extract text from the PDF. As far as I know, the pytesseract library is one of the more popular libraries for OCR."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "KjlUTGhNsYa_"
      },
      "source": [
        "import requests\n",
        "import pdf2image\n",
        "import pytesseract\n",
        "\n",
        "pdf = requests.get('https://arxiv.org/pdf/2110.03526.pdf')\n",
        "doc = pdf2image.convert_from_bytes(pdf.content)\n",
        "\n",
        "# Get the article text\n",
        "article = []\n",
        "for page_number, page_data in enumerate(doc):\n",
        "    txt = pytesseract.image_to_string(page_data).encode(\"utf-8\")\n",
        "    # Sixth page are only references\n",
        "    if page_number < 6:\n",
        "      article.append(txt.decode(\"utf-8\"))\n",
        "article_txt = \" \".join(article)\n"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "unUt1x0_nqJj"
      },
      "source": [
        "## Text preprocessing\n",
        "Now that we have the article content available, we will go ahead and remove section titles and figure descriptions from the text. Next, we will split the text into sentences."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "mdDKtiF4GLrR",
        "outputId": "7340435e-ef8b-4054-cb46-086a784e616c"
      },
      "source": [
        "import nltk\n",
        "nltk.download('punkt')\n",
        "\n",
        "def clean_text(text):\n",
        "  \"\"\"Remove section titles and figure descriptions from text\"\"\"\n",
        "  clean = \"\\n\".join([row for row in text.split(\"\\n\") if (len(row.split(\" \"))) > 3 and not (row.startswith(\"(a)\"))\n",
        "                    and not row.startswith(\"Figure\")])\n",
        "  return clean\n",
        "\n",
        "text = article_txt.split(\"INTRODUCTION\")[1]\n",
        "ctext = clean_text(text)\n",
        "sentences = nltk.tokenize.sent_tokenize(ctext)"
      ],
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "[nltk_data] Downloading package punkt to /root/nltk_data...\n",
            "[nltk_data]   Package punkt is already up-to-date!\n"
          ]
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "foNDUZDwnuYX"
      },
      "source": [
        "## Biomedical named entity linking\n",
        "Now comes the exciting part. For those new to NLP and named entity recognition and linking, let's start with some basics. Named entity recognition techniques are used to detect relevant entities or concepts in the text. For example, in the biomedical domain, we want to identify various genes, drugs, diseases, and other concepts in the text.\n",
        "\n",
        "In this example, the NLP model identified genes, diseases, drugs, species, mutations, and pathways in the text. As mentioned, this process is called named entity recognition. An upgrade to the named entity recognition is the so-called named entity linking. The named entity linking technique detects relevant concepts in the text and tries to map them to the target knowledge base. In the biomedical domain, some of the target knowledge bases are:\n",
        "* MESH\n",
        "* CHEBI\n",
        "* OMIM\n",
        "* ENSEMBL\n",
        "* and others\n",
        "\n",
        "Why would we want to link medical entities to a target knowledge base? The primary reason is that it helps us deal with entity disambiguation. For example, we don't want separate entities in the graph representing ascorbic acid and vitamin C as domain experts can tell you those are the same thing. The secondary reason is that by mapping concepts to a target knowledge base, we can enrich our graph model by fetching information about the mapped concepts from the target knowledge base. If we use the ascorbic acid example again, we could easily fetch additional information from the CHEBI database if we already know its CHEBI id.\n",
        "\n",
        "I've been looking for a decent open-source pre-trained biomedical named entity linking for some time. Lots of NLP models focus on extracting only a specific subset of medical concepts like genes or diseases. It is even rarer to find a model that detects most medical concepts and links them to a target knowledge base. Luckily I've stumbled upon BERN[1], a neural biomedical entity recognition and multi-type normalization tool. If I understand correctly, it is a fine-tuned BioBert model with various named entity linking models integrated for mapping concepts to biomedical target knowledge bases. Not only that, but they also provide a free REST endpoint, so we don't have to deal with the headache of getting the dependencies and the model to work. The biomedical named entity recognition visualization I've used above was created using the BERN model, so we know it detects genes, diseases, drugs, species, mutations, and pathways in the text.\n",
        "Unfortunately, the BERN model does not assign target knowledge base ids to all concepts. So I've prepared a script that first looks if a distinct id is given for a concept, and if it is not, it will use the entity name as the id. We will also compute the sha256 of the text of sentences to identify specific sentences easier later when we will be doing relation extraction."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "JBdjSIIkK30M"
      },
      "source": [
        "import hashlib\n",
        "\n",
        "def query_raw(text, url=\"https://bern.korea.ac.kr/plain\"):\n",
        "  \"\"\"Biomedical entity linking API\"\"\"\n",
        "  return requests.post(url, data={'sample_text': text}).json()\n",
        "\n",
        "entity_list = []\n",
        "# The last sentence is invalid\n",
        "for s in sentences[:-1]:\n",
        "  entity_list.append(query_raw(s))\n",
        "\n",
        "parsed_entities = []\n",
        "for entities in entity_list:\n",
        "  e = []\n",
        "  # If there are not entities in the text\n",
        "  if not entities.get('denotations'):\n",
        "    parsed_entities.append({'text':entities['text'], 'text_sha256': hashlib.sha256(entities['text'].encode('utf-8')).hexdigest()})\n",
        "    continue\n",
        "  for entity in entities['denotations']:\n",
        "    other_ids = [id for id in entity['id'] if not id.startswith(\"BERN\")]\n",
        "    entity_type = entity['obj']\n",
        "    entity_name = entities['text'][entity['span']['begin']:entity['span']['end']]\n",
        "    try:\n",
        "      entity_id = [id for id in entity['id'] if id.startswith(\"BERN\")][0]\n",
        "    except IndexError:\n",
        "      entity_id = entity_name\n",
        "    e.append({'entity_id': entity_id, 'other_ids': other_ids, 'entity_type': entity_type, 'entity': entity_name})\n",
        "  parsed_entities.append({'entities':e, 'text':entities['text'], 'text_sha256': hashlib.sha256(entities['text'].encode('utf-8')).hexdigest()})"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "T2uI551Mn8X-"
      },
      "source": [
        "I've inspected the results of the named entity linking, and as expected, it is not perfect. For example, it does not identify stem cells as a medical concept. On the other hand, it detected a single entity named \"heart, brain, nerves, and kidney\". However, BERN is still the best open-source biomedical model I could find during my investigation.\n",
        "## Construct a knowledge graph\n",
        "Before looking at relation extraction techniques, we will construct a biomedical knowledge graph using only entities and examine the possible applications. As mentioned, I have prepared a Google Colab notebook that you can use to follow the code examples in this post. To store our graph, we will be using Neo4j. You don't have to deal with preparing a local Neo4j environment. Instead, you can use a free Neo4j Sandbox instance.\n",
        "\n",
        "Start the Blank project in the sandbox and copy the connection details to the Colab notebook. https://sandbox.neo4j.com/?usecase=blank-sandbox\n",
        "\n",
        "Now you can go ahead and prepare the Neo4j connection in the notebook."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "mmV_RMPBF4z6"
      },
      "source": [
        "from neo4j import GraphDatabase\n",
        "import pandas as pd\n",
        "\n",
        "host = 'bolt://3.236.182.55:7687'\n",
        "user = 'neo4j'\n",
        "password = 'hydrometer-ditches-windings'\n",
        "driver = GraphDatabase.driver(host,auth=(user, password))\n",
        "\n",
        "def neo4j_query(query, params=None):\n",
        "    with driver.session() as session:\n",
        "        result = session.run(query, params)\n",
        "        return pd.DataFrame([r.values() for r in result], columns=result.keys())"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "nNp94NNVoHB8"
      },
      "source": [
        "We will start by importing the author and the article into the graph. The article node will contain only the title."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 32
        },
        "id": "nw0XFDBSFoNk",
        "outputId": "928e48e3-43b5-4818-9f60-5dfcd24dff91"
      },
      "source": [
        "author = article_txt.split(\"\\n\")[0]\n",
        "title = \" \".join(article_txt.split(\"\\n\")[2:4])\n",
        "\n",
        "neo4j_query(\"\"\"\n",
        "MERGE (a:Author{name:$author})\n",
        "MERGE (b:Article{title:$title})\n",
        "MERGE (a)-[:WROTE]->(b)\n",
        "\"\"\", {'title':title, 'author':author})"
      ],
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/html": [
              "<div>\n",
              "<style scoped>\n",
              "    .dataframe tbody tr th:only-of-type {\n",
              "        vertical-align: middle;\n",
              "    }\n",
              "\n",
              "    .dataframe tbody tr th {\n",
              "        vertical-align: top;\n",
              "    }\n",
              "\n",
              "    .dataframe thead th {\n",
              "        text-align: right;\n",
              "    }\n",
              "</style>\n",
              "<table border=\"1\" class=\"dataframe\">\n",
              "  <thead>\n",
              "    <tr style=\"text-align: right;\">\n",
              "      <th></th>\n",
              "    </tr>\n",
              "  </thead>\n",
              "  <tbody>\n",
              "  </tbody>\n",
              "</table>\n",
              "</div>"
            ],
            "text/plain": [
              "Empty DataFrame\n",
              "Columns: []\n",
              "Index: []"
            ]
          },
          "metadata": {},
          "execution_count": 5
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "4IQuBOizoJ5K"
      },
      "source": [
        "You can import the sentences and mentioned entities by executing the following Cypher query:"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 32
        },
        "id": "Ef0HI6VEQW1R",
        "outputId": "4cc01e9a-9262-46d7-e327-3a0ded7af1b5"
      },
      "source": [
        "neo4j_query(\"\"\"\n",
        "MATCH (a:Article)\n",
        "UNWIND $data as row\n",
        "MERGE (s:Sentence{id:row.text_sha256})\n",
        "SET s.text = row.text\n",
        "MERGE (a)-[:HAS_SENTENCE]->(s)\n",
        "WITH s, row.entities as entities\n",
        "UNWIND entities as entity\n",
        "MERGE (e:Entity{id:entity.entity_id})\n",
        "ON CREATE SET e.other_ids = entity.other_ids,\n",
        "              e.name = entity.entity,\n",
        "              e.type = entity.entity_type\n",
        "MERGE (s)-[m:MENTIONS]->(e)\n",
        "ON CREATE SET m.count = 1\n",
        "ON MATCH SET m.count = m.count + 1\n",
        "\"\"\", {'data': parsed_entities})"
      ],
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/html": [
              "<div>\n",
              "<style scoped>\n",
              "    .dataframe tbody tr th:only-of-type {\n",
              "        vertical-align: middle;\n",
              "    }\n",
              "\n",
              "    .dataframe tbody tr th {\n",
              "        vertical-align: top;\n",
              "    }\n",
              "\n",
              "    .dataframe thead th {\n",
              "        text-align: right;\n",
              "    }\n",
              "</style>\n",
              "<table border=\"1\" class=\"dataframe\">\n",
              "  <thead>\n",
              "    <tr style=\"text-align: right;\">\n",
              "      <th></th>\n",
              "    </tr>\n",
              "  </thead>\n",
              "  <tbody>\n",
              "  </tbody>\n",
              "</table>\n",
              "</div>"
            ],
            "text/plain": [
              "Empty DataFrame\n",
              "Columns: []\n",
              "Index: []"
            ]
          },
          "metadata": {},
          "execution_count": 9
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ZX-1delsoN2v"
      },
      "source": [
        "## Knowledge graph applications\n",
        "Even without the relation extraction flow, there are already a couple of use-cases for our graph.\n",
        "### Search engine\n",
        "We could use our graph as a search engine. For example, you could use the following Cypher query to find sentences or articles that mention a specific medical entity."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 80
        },
        "id": "yGE-gnuBYx-Y",
        "outputId": "f091c23d-bfe5-43e3-a2e3-28cd527710ea"
      },
      "source": [
        "neo4j_query(\"\"\"\n",
        "MATCH (e:Entity)<-[:MENTIONS]-(s:Sentence)\n",
        "WHERE e.name = \"autoimmune diseases\"\n",
        "RETURN s.text as result\n",
        "\"\"\")"
      ],
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/html": [
              "<div>\n",
              "<style scoped>\n",
              "    .dataframe tbody tr th:only-of-type {\n",
              "        vertical-align: middle;\n",
              "    }\n",
              "\n",
              "    .dataframe tbody tr th {\n",
              "        vertical-align: top;\n",
              "    }\n",
              "\n",
              "    .dataframe thead th {\n",
              "        text-align: right;\n",
              "    }\n",
              "</style>\n",
              "<table border=\"1\" class=\"dataframe\">\n",
              "  <thead>\n",
              "    <tr style=\"text-align: right;\">\n",
              "      <th></th>\n",
              "      <th>result</th>\n",
              "    </tr>\n",
              "  </thead>\n",
              "  <tbody>\n",
              "    <tr>\n",
              "      <th>0</th>\n",
              "      <td>These cells, later found to be hematopoietic s...</td>\n",
              "    </tr>\n",
              "  </tbody>\n",
              "</table>\n",
              "</div>"
            ],
            "text/plain": [
              "                                              result\n",
              "0  These cells, later found to be hematopoietic s..."
            ]
          },
          "metadata": {},
          "execution_count": 10
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Qz-9K2eooSpW"
      },
      "source": [
        "### Co-occurrence analysis\n",
        "The second option is the co-occurrence analysis. You could define co-occurrence between medical entities if they appear in the same sentence or article. I've found an article[2] that uses the medical co-occurrence network to predict new possible connections between medical entities.\n",
        "Link prediction in a MeSH co-occurrence network: preliminary results - PubMed\n",
        "Literature-based discovery (LBD) refers to automatic discovery of implicit relations from the scientific literature.\n",
        "You could use the following Cypher query to find entities that often co-occur in the same sentence."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 142
        },
        "id": "wITWy7n_iTcY",
        "outputId": "c7af7d2f-de77-48fc-def0-aba90e9133c0"
      },
      "source": [
        "neo4j_query(\"\"\"\n",
        "MATCH (e1:Entity)<-[:MENTIONS]-()-[:MENTIONS]->(e2:Entity)\n",
        "WHERE id(e1) < id(e2)\n",
        "RETURN e1.name as entity1, e2.name as entity2, count(*) as cooccurrence\n",
        "ORDER BY cooccurrence\n",
        "DESC LIMIT 3\n",
        "\"\"\")"
      ],
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/html": [
              "<div>\n",
              "<style scoped>\n",
              "    .dataframe tbody tr th:only-of-type {\n",
              "        vertical-align: middle;\n",
              "    }\n",
              "\n",
              "    .dataframe tbody tr th {\n",
              "        vertical-align: top;\n",
              "    }\n",
              "\n",
              "    .dataframe thead th {\n",
              "        text-align: right;\n",
              "    }\n",
              "</style>\n",
              "<table border=\"1\" class=\"dataframe\">\n",
              "  <thead>\n",
              "    <tr style=\"text-align: right;\">\n",
              "      <th></th>\n",
              "      <th>entity1</th>\n",
              "      <th>entity2</th>\n",
              "      <th>cooccurrence</th>\n",
              "    </tr>\n",
              "  </thead>\n",
              "  <tbody>\n",
              "    <tr>\n",
              "      <th>0</th>\n",
              "      <td>skin diseases</td>\n",
              "      <td>diabetic ulcers</td>\n",
              "      <td>2</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>1</th>\n",
              "      <td>chronic wounds</td>\n",
              "      <td>diabetic ulcers</td>\n",
              "      <td>2</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>2</th>\n",
              "      <td>skin diseases</td>\n",
              "      <td>chronic wounds</td>\n",
              "      <td>2</td>\n",
              "    </tr>\n",
              "  </tbody>\n",
              "</table>\n",
              "</div>"
            ],
            "text/plain": [
              "          entity1          entity2  cooccurrence\n",
              "0   skin diseases  diabetic ulcers             2\n",
              "1  chronic wounds  diabetic ulcers             2\n",
              "2   skin diseases   chronic wounds             2"
            ]
          },
          "metadata": {},
          "execution_count": 11
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "6xkAqw9foZvF"
      },
      "source": [
        "Obviously, the results would be better if we analyzed thousands or more articles.\n",
        "### Inspect author expertise\n",
        "You could also use this graph to find the author's expertise by examining the medical entities they most frequently write about. With this information, you could also suggest future collaborations.\n",
        "Execute the following Cypher query to inspect which medical entities our single author mentioned in the research paper."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 204
        },
        "id": "6Bsa7o8z0z8Q",
        "outputId": "76275848-269f-4507-94f9-6acef03ea7f3"
      },
      "source": [
        "neo4j_query(\"\"\"\n",
        "MATCH (a:Author)-[:WROTE]->()-[:HAS_SENTENCE]->()-[:MENTIONS]->(e:Entity)\n",
        "RETURN a.name as author, e.name as entity, count(*) as count\n",
        "ORDER BY count DESC\n",
        "LIMIT 5\n",
        "\"\"\")"
      ],
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/html": [
              "<div>\n",
              "<style scoped>\n",
              "    .dataframe tbody tr th:only-of-type {\n",
              "        vertical-align: middle;\n",
              "    }\n",
              "\n",
              "    .dataframe tbody tr th {\n",
              "        vertical-align: top;\n",
              "    }\n",
              "\n",
              "    .dataframe thead th {\n",
              "        text-align: right;\n",
              "    }\n",
              "</style>\n",
              "<table border=\"1\" class=\"dataframe\">\n",
              "  <thead>\n",
              "    <tr style=\"text-align: right;\">\n",
              "      <th></th>\n",
              "      <th>author</th>\n",
              "      <th>entity</th>\n",
              "      <th>count</th>\n",
              "    </tr>\n",
              "  </thead>\n",
              "  <tbody>\n",
              "    <tr>\n",
              "      <th>0</th>\n",
              "      <td>Mohammadreza Ahmadi</td>\n",
              "      <td>collagen</td>\n",
              "      <td>9</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>1</th>\n",
              "      <td>Mohammadreza Ahmadi</td>\n",
              "      <td>burns</td>\n",
              "      <td>4</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>2</th>\n",
              "      <td>Mohammadreza Ahmadi</td>\n",
              "      <td>skin diseases</td>\n",
              "      <td>4</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>3</th>\n",
              "      <td>Mohammadreza Ahmadi</td>\n",
              "      <td>collagenase enzymes</td>\n",
              "      <td>2</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>4</th>\n",
              "      <td>Mohammadreza Ahmadi</td>\n",
              "      <td>Epidermolysis bullosa</td>\n",
              "      <td>2</td>\n",
              "    </tr>\n",
              "  </tbody>\n",
              "</table>\n",
              "</div>"
            ],
            "text/plain": [
              "                author                 entity  count\n",
              "0  Mohammadreza Ahmadi               collagen      9\n",
              "1  Mohammadreza Ahmadi                  burns      4\n",
              "2  Mohammadreza Ahmadi          skin diseases      4\n",
              "3  Mohammadreza Ahmadi    collagenase enzymes      2\n",
              "4  Mohammadreza Ahmadi  Epidermolysis bullosa      2"
            ]
          },
          "metadata": {},
          "execution_count": 18
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "DC-q2-WKodFt"
      },
      "source": [
        "## Relation extraction\n",
        "Now we will try to extract relations between medical concepts. From my experience, the relation extraction is at least an order of magnitude harder than named entity extraction. If you shouldn't expect perfect results with named entity linking, then you can definitely expect some mistakes with the relation extraction technique.\n",
        "\n",
        "I've been looking for available biomedical relation extraction models but found nothing that works out of the box or doesn't require fine-tuning. It seems that the field of relation extraction is at the cutting edge, and hopefully, we'll see more attention about it in the future. Unfortunately, I'm not an NLP expert, so I avoided fine-tuning my own model. Instead, we will be using the zero-shot relation extractor based on the paper Exploring the zero-shot limit of FewRel[3]. While I wouldn't recommend to put this model into production, it is good enough for a simple demonstration. The model is available on HuggingFace, so we don't have to deal with training or setting up the model."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "-rSodX2-jF2q",
        "outputId": "349b0903-93b8-4a55-a941-9a52d2005c93"
      },
      "source": [
        "from transformers import AutoTokenizer\n",
        "from zero_shot_re import RelTaggerModel, RelationExtractor\n",
        "\n",
        "model = RelTaggerModel.from_pretrained(\"fractalego/fewrel-zero-shot\")\n",
        "tokenizer = AutoTokenizer.from_pretrained(\"bert-large-uncased-whole-word-masking-finetuned-squad\")\n",
        "relations = ['associated', 'interacts']\n",
        "extractor = RelationExtractor(model, tokenizer, relations)"
      ],
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stderr",
          "text": [
            "Some weights of the model checkpoint at bert-large-uncased-whole-word-masking-finetuned-squad were not used when initializing BertModel: ['qa_outputs.weight', 'qa_outputs.bias']\n",
            "- This IS expected if you are initializing BertModel from the checkpoint of a model trained on another task or with another architecture (e.g. initializing a BertForSequenceClassification model from a BertForPreTraining model).\n",
            "- This IS NOT expected if you are initializing BertModel from the checkpoint of a model that you expect to be exactly identical (initializing a BertForSequenceClassification model from a BertForSequenceClassification model).\n"
          ]
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "wyJuh8Yjogy6"
      },
      "source": [
        "With the zero-shot relation extractor, you can define which relations you would like to detect. In this example, I've used the associated and interacts relationships. I've also tried more specific relationship types such as treats, causes, and others, but the results were not great.\n",
        "\n",
        "With this model, you have to define between which pairs of entities you would like to detect relationships. We will use the results of the named entity linking as an input to the relation extraction process. First, we find all the sentences where two or more entities are mentioned and then run them through the relation extraction model to extract any connections. I've also defined a threshold value of 0.85, meaning that if a model predicts a link between entities with a probability lower than 0.85, we'll ignore the prediction."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "YZeYC8HNjaPG"
      },
      "source": [
        "import itertools\n",
        "# Candidate sentence where there is more than a single entity present\n",
        "candidates = [s for s in parsed_entities if (s.get('entities')) and (len(s['entities']) > 1)]\n",
        "predicted_rels = []\n",
        "for c in candidates:\n",
        "  combinations = itertools.combinations([{'name':x['entity'], 'id':x['entity_id']} for x in c['entities']], 2)\n",
        "  for combination in list(combinations):\n",
        "    try:\n",
        "      ranked_rels = extractor.rank(text=c['text'].replace(\",\", \" \"), head=combination[0]['name'], tail=combination[1]['name'])\n",
        "      # Define threshold for the most probable relation\n",
        "      if ranked_rels[0][1] > 0.85:\n",
        "        predicted_rels.append({'head': combination[0]['id'], 'tail': combination[1]['id'], 'type':ranked_rels[0][0], 'source': c['text_sha256']})\n",
        "    except:\n",
        "      pass\n",
        "\n",
        "neo4j_query(\"\"\"\n",
        "UNWIND $data as row\n",
        "MATCH (source:Entity {id: row.head})\n",
        "MATCH (target:Entity {id: row.tail})\n",
        "MATCH (text:Sentence {id: row.source})\n",
        "MERGE (source)-[:REL]->(r:Relation {type: row.type})-[:REL]->(target)\n",
        "MERGE (text)-[:MENTIONS]->(r)\n",
        "\"\"\", {'data': predicted_rels})"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "FubQf_MHokOE"
      },
      "source": [
        "You can examine the extracted relationships between entities and the source text with the following Cypher query:"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 173
        },
        "id": "f5wD_PChuCcc",
        "outputId": "954f094d-47b9-4124-beab-fbf189bfdc03"
      },
      "source": [
        "neo4j_query(\"\"\"\n",
        "MATCH (s:Entity)-[:REL]->(r:Relation)-[:REL]->(t:Entity), (r)<-[:MENTIONS]-(st:Sentence)\n",
        "RETURN s.name as source_entity, t.name as target_entity, r.type as type, st.text as source_text\n",
        "\"\"\")"
      ],
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/html": [
              "<div>\n",
              "<style scoped>\n",
              "    .dataframe tbody tr th:only-of-type {\n",
              "        vertical-align: middle;\n",
              "    }\n",
              "\n",
              "    .dataframe tbody tr th {\n",
              "        vertical-align: top;\n",
              "    }\n",
              "\n",
              "    .dataframe thead th {\n",
              "        text-align: right;\n",
              "    }\n",
              "</style>\n",
              "<table border=\"1\" class=\"dataframe\">\n",
              "  <thead>\n",
              "    <tr style=\"text-align: right;\">\n",
              "      <th></th>\n",
              "      <th>source_entity</th>\n",
              "      <th>target_entity</th>\n",
              "      <th>type</th>\n",
              "      <th>source_text</th>\n",
              "    </tr>\n",
              "  </thead>\n",
              "  <tbody>\n",
              "    <tr>\n",
              "      <th>0</th>\n",
              "      <td>skin diseases</td>\n",
              "      <td>chronic wounds</td>\n",
              "      <td>associated</td>\n",
              "      <td>Many people with skin diseases such as chronic...</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>1</th>\n",
              "      <td>skin diseases</td>\n",
              "      <td>diabetic ulcers</td>\n",
              "      <td>associated</td>\n",
              "      <td>Many people with skin diseases such as chronic...</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>2</th>\n",
              "      <td>leukemia</td>\n",
              "      <td>autoimmune diseases</td>\n",
              "      <td>associated</td>\n",
              "      <td>These cells, later found to be hematopoietic s...</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>3</th>\n",
              "      <td>ADSCs</td>\n",
              "      <td>DFs proteins</td>\n",
              "      <td>interacts</td>\n",
              "      <td>Furthermore, the primary sources of extracellu...</td>\n",
              "    </tr>\n",
              "  </tbody>\n",
              "</table>\n",
              "</div>"
            ],
            "text/plain": [
              "   source_entity  ...                                        source_text\n",
              "0  skin diseases  ...  Many people with skin diseases such as chronic...\n",
              "1  skin diseases  ...  Many people with skin diseases such as chronic...\n",
              "2       leukemia  ...  These cells, later found to be hematopoietic s...\n",
              "3          ADSCs  ...  Furthermore, the primary sources of extracellu...\n",
              "\n",
              "[4 rows x 4 columns]"
            ]
          },
          "metadata": {},
          "execution_count": 17
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "IzUK-b-CoqYm"
      },
      "source": [
        "As mentioned, the NLP model I've used to extract relations is not perfect, and since I am not a medical doctor, I don't know how many connections it missed. However, the ones it detected seem reasonable.\n",
        "## External database enrichment\n",
        "As I mentioned before, we can still use the external databases like CHEBI or MESH to enrich our graph. For example, our graph contains a medical entity Epidermolysis bullosa and we also know its MeSH id.\n",
        "\n",
        "The Cypher query to fetch the information from MeSH REST endpoint is:"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 80
        },
        "id": "I-gbM4r30odL",
        "outputId": "5832a35b-792d-49d3-cbff-4eef56c2d4ba"
      },
      "source": [
        "# mesh enrichment\n",
        "neo4j_query(\"\"\"\n",
        "MATCH (e:Entity)\n",
        "WHERE e.name = \"Epidermolysis bullosa\"\n",
        "WITH e,\n",
        "    [id in e.other_ids WHERE id contains \"MESH\" | split(id,\":\")[1]][0] as meshId\n",
        "CALL apoc.load.json(\"https://id.nlm.nih.gov/mesh/lookup/details?descriptor=\" + meshId) YIELD value\n",
        "RETURN value\n",
        "\"\"\")"
      ],
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/html": [
              "<div>\n",
              "<style scoped>\n",
              "    .dataframe tbody tr th:only-of-type {\n",
              "        vertical-align: middle;\n",
              "    }\n",
              "\n",
              "    .dataframe tbody tr th {\n",
              "        vertical-align: top;\n",
              "    }\n",
              "\n",
              "    .dataframe thead th {\n",
              "        text-align: right;\n",
              "    }\n",
              "</style>\n",
              "<table border=\"1\" class=\"dataframe\">\n",
              "  <thead>\n",
              "    <tr style=\"text-align: right;\">\n",
              "      <th></th>\n",
              "      <th>value</th>\n",
              "    </tr>\n",
              "  </thead>\n",
              "  <tbody>\n",
              "    <tr>\n",
              "      <th>0</th>\n",
              "      <td>{'qualifiers': [{'resource': 'http://id.nlm.ni...</td>\n",
              "    </tr>\n",
              "  </tbody>\n",
              "</table>\n",
              "</div>"
            ],
            "text/plain": [
              "                                               value\n",
              "0  {'qualifiers': [{'resource': 'http://id.nlm.ni..."
            ]
          },
          "metadata": {},
          "execution_count": 19
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "z4ksXgoXoyXd"
      },
      "source": [
        "## Knowledge graph as machine learning data input\n",
        "As a final thought, I will quickly walk you through how you could use the biomedical knowledge graph as an input to a machine learning workflow. In recent years, there has been a lot of research and advancement in the node embedding field. Node embedding models translate the network topology into embedding space.\n",
        "\n",
        "Suppose you constructed a biomedical knowledge graph containing medical entities and concepts, their relations, and enrichment from various medical databases. You could use node embedding techniques to learn the node representations, which are fixed-length vectors, and input them into your machine learning workflow. Various applications are using this approach ranging from drug repurposing to drug side or adverse effect predictions. I've found a research paper that uses link prediction for potential treatments of new diseases[4].\n",
        "## Conclusion\n",
        "The biomedical domain is a prime example where knowledge graphs are applicable. There are many applications ranging from simple search engines to more complicated machine learning workflows. Hopefully, by reading this blog post, you came up with some ideas on how you could use biomedical knowledge graphs to support your applications."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "kdOI-SDC5CNs"
      },
      "source": [
        ""
      ],
      "execution_count": null,
      "outputs": []
    }
  ]
}